﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Globalization;
using System.Threading;
using ExpressionEvaluator.Expceptions;

namespace ExpressionEvaluator
{
	public class Evalizer
	{

		public Evalizer()
		{
			_NamespacePatterns = new HashSet<string>();
			_Assemblies = new HashSet<string>();
			_Types = new HashSet<Type>();
			// add system namespace for lookup for system types
			AddLookupNamespace("System");
			AddLookupAssembly("mscorlib.dll");
		}

		/// <summary>
		/// List of namespace patterns for resolving types.
		/// </summary>
		private HashSet<string> _NamespacePatterns;

		/// <summary>
		/// List of assemblies names for resolving types;
		/// </summary>
		private HashSet<string> _Assemblies;

		private HashSet<Type> _Types;

		private Dictionary<string, string> _SubstituteRegex;

		/// <summary>
		/// Adds the lookup namespace.
		/// </summary>
		/// <param name="namespaceName">Name of the namespace.</param>
		/// <returns>This instance for fluent interface.</returns>
		public Evalizer AddLookupNamespace(string namespaceName)
		{
			if (!_NamespacePatterns.Contains(namespaceName))
			{
				_NamespacePatterns.Add(namespaceName);
			}	
			return this;
		}


		/// <summary>
		/// Adds the lookup assembly.
		/// </summary>
		/// <param name="assembly">The assembly.</param>
		/// <returns>This instance for fluent interface.</returns>
		public Evalizer AddLookupAssembly(string assembly)
		{
			if (!_Assemblies.Contains(assembly))
			{
				_Assemblies.Add(assembly);
			}
			return this;
		}

		/// <summary>
		/// Adds the lookup type.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <returns>This instance for fluent interface.</returns>
		public Evalizer AddLookupType(Type type)
		{
			if (!_Types.Contains(type))
			{
				_Types.Add(type);
			}
			return this;
		}

		/// <summary>
		/// Adds the lookup types.
		/// </summary>
		/// <param name="types">The types.</param>
		/// <returns>This instance for fluent interface.</returns>
		public Evalizer AddLookupTypes(IEnumerable<Type> types)
		{
			foreach (var type in types)
			{
				AddLookupType(type);
			}
			return this;
		}

		/// <summary>
		/// Evalizes the specified lambda.
		/// </summary>
		/// <typeparam name="TDelegate">The type of the delegate.</typeparam>
		/// <param name="lambda">The lambda.</param>
		/// <returns>Lamda expression as string.</returns>
		public string Evalize<TDelegate>(Expression<TDelegate> lambda)
		{
			return EvalizeExpression(lambda.Body);
		}

		/// <summary>
		/// Evalizes the expression.
		/// </summary>
		/// <param name="expression">The expression.</param>
		/// <returns>Expression as string.</returns>
		public string EvalizeExpression(Expression expression)
		{
			_SubstituteRegex = new Dictionary<string, string>();
			CultureInfo actualCultureInfo = Thread.CurrentThread.CurrentCulture;
			Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

			// currently isSimplified is not usefull
			bool isSimplified;
			string result = Simplify(expression, out isSimplified).ToString();

			Thread.CurrentThread.CurrentCulture = actualCultureInfo;
			foreach (var item in _SubstituteRegex)
			{
				result = Regex.Replace(result, item.Key, item.Value);
			}
			return result.Replace("\\", "\\\\");
		}
		/// <summary>
		/// Evaluates the specified expression.
		/// </summary>
		/// <param name="expr">The expression.</param>
		/// <returns>Evaluated value of expression.</returns>
		private static object Evaluate(Expression expr)
		{
			return Expression.Lambda(expr).Compile().DynamicInvoke(null);
		}

		/// <summary>
		/// Simplifies the specified part.
		/// </summary>
		/// <param name="part">The part.</param>
		/// <returns>Simplified expression</returns>
		private Expression Simplify(Expression part, out bool isSimplified)
		{
			isSimplified = false;
			if (part is BinaryExpression)
			{
				BinaryExpression binExpression = part as BinaryExpression;
				bool isSimplifiedRight, isSimplifiedLeft;
                Expression left = Simplify(binExpression.Left, out isSimplifiedLeft);
                Expression right = Simplify(binExpression.Right, out isSimplifiedRight);
				// we don't need to pass simplified info (let predict, that result of binary experssion is not simple type)
				isSimplified = false;
				return Expression.MakeBinary(
					binExpression.NodeType,
					left,
					right,
					binExpression.IsLiftedToNull,
					binExpression.Method,
					binExpression.Conversion);
			}
			else if (part is ConstantExpression)
			{
				ConstantExpression constExpression = part as ConstantExpression;
				if (IsSimplifyType(constExpression))
				{
					isSimplified = true;
					return BuildConstant(part);
				}
				else
				{
					return BuildConstant(constExpression, true);
				}
			}
			else if (part is MemberExpression)
			{
				MemberExpression memberExpression = part as MemberExpression;

				bool isMemberSimplified;
				Expression expressionForMember = Simplify(memberExpression.Expression, out isMemberSimplified);
				if (isMemberSimplified)
				{
					isSimplified = true;
					return BuildConstant(part);
				}
				else
				{
					return Expression.MakeMemberAccess(expressionForMember, memberExpression.Member);
				}
			}
			else if (part is MethodCallExpression)
			{
				MethodCallExpression methodExpression = part as MethodCallExpression;
				if (IsSimplifyType(methodExpression))
				{
					return BuildConstant(part);
				}
				else
				{
					List<Expression> arguments = new List<Expression>(methodExpression.Arguments.Count);
					foreach (Expression argument in methodExpression.Arguments)
					{
						// no use for simplified result of argument
						bool isArgumentSimplified;
						arguments.Add(Simplify(argument, out isArgumentSimplified));
					}

					bool isMethodSimplified;
					Expression objectExpression = Simplify(methodExpression.Object, out isMethodSimplified);

					if (isMethodSimplified)
					{
						isSimplified = true;
						return BuildConstant(part);
					}
					// if static and not extension method
					if (methodExpression.Method.IsStatic && !methodExpression.Method.IsDefined(typeof(System.Runtime.CompilerServices.ExtensionAttribute), false))
					{
						CreateReplacePatternForStaticMethod(methodExpression.Method);
					}
					return Expression.Call(
						objectExpression,
						methodExpression.Method,
						arguments);
				}
			}
			else if (part is NewExpression)
			{
				NewExpression newExpression = part as NewExpression;
				string replacePattern = string.Format("([^\\.]|^)(new {0})(\\()", newExpression.Type.Name);
				if (!_SubstituteRegex.ContainsKey(replacePattern) && (newExpression.Type.Namespace != null))
				{
					_SubstituteRegex.Add(
						replacePattern,
						string.Format("$1new {0}.{1}$3",
							newExpression.Type.Namespace,
							newExpression.Type.Name));
				}
				List<Expression> arguments = new List<Expression>(newExpression.Arguments.Count);


				foreach (Expression argument in newExpression.Arguments)
				{
					// no use for simplified result of argument
					bool isArgumentSimplified;
					arguments.Add(Simplify(argument, out isArgumentSimplified));
				}
				if (newExpression.Constructor == null)
				{
					return Expression.New(newExpression.Type);
				}
				else if (newExpression.Members == null)
				{
					return Expression.New(
						newExpression.Constructor,
						arguments);
				}
				else
				{
					return Expression.New(
						newExpression.Constructor,
						arguments,
						newExpression.Members);
				}
			}
			else if (part is UnaryExpression)
			{
				UnaryExpression unaryExpression = part as UnaryExpression;
				bool isUnarySimplified;
				Expression expressionForUnary = Simplify(unaryExpression.Operand, out isUnarySimplified);
				if (isUnarySimplified)
				{
					isSimplified = true;
					return BuildConstant(part);
				}
				else
				{
					if (unaryExpression.NodeType == ExpressionType.Convert)
					{
						MethodInfo convertMethod = typeof(Convert).GetMethod("To" + unaryExpression.Type.Name, new Type[] { expressionForUnary.Type });
						if (convertMethod == null)
						{
							throw new EvalizerException(string.Format("Unable to find convert function for type '{0}' to type '{1}'.", expressionForUnary.Type, unaryExpression.Type));
						}
						CreateReplacePatternForStaticMethod(convertMethod);
						return Expression.Call(
							convertMethod,
							expressionForUnary);
					}
					return Expression.MakeUnary(unaryExpression.NodeType, expressionForUnary, unaryExpression.Type, unaryExpression.Method);
				}
			}
			else if (part is LambdaExpression)
			{
				LambdaExpression lambdaExpression = part as LambdaExpression;
				if (IsSimplifyType(lambdaExpression))
				{
					return BuildConstant(part);
				}
				else
				{
					// no use for lambda simplified info
					bool isLambdaSimplified;
					Expression expressionForLambda = Simplify(lambdaExpression.Body, out isLambdaSimplified);
					return Expression.Lambda(lambdaExpression.Type, expressionForLambda, lambdaExpression.Parameters);
				}
			}
			else if (part is ParameterExpression)
			{
				return part;
			}
			if (part == null)
			{
				return part;
			}
			// all unknown expression convert to constant
			isSimplified = true;
			return BuildConstant(part);
		}

		/// <summary>
		/// Creates the replace pattern for static method.
		/// </summary>
		/// <param name="method">The method.</param>
		private void CreateReplacePatternForStaticMethod(MethodInfo method)
		{
			string replacePattern = string.Format("([^\\.]|^)({0})(\\()", method.Name);
			if (!_SubstituteRegex.ContainsKey(replacePattern))
			{
				_SubstituteRegex.Add(
					replacePattern,
					string.Format("$1{0}.{1}.{2}$3",
						method.DeclaringType.Namespace,
						method.DeclaringType.Name,
						method.Name));
			}
		}

		/// <summary>
		/// Builds the constant.
		/// </summary>
		/// <param name="expression">The expression.</param>
		/// <returns>Expression for constant.</returns>
		private Expression BuildConstant(Expression expression)
		{
			return BuildConstant(expression, false);
		}

		/// <summary>
		/// Builds the constant.
		/// </summary>
		/// <param name="expression">The expression.</param>
		/// <param name="isFromConstant">if set to <c>true</c> [is from constant].</param>
		/// <returns>Expression for constant.</returns>
		private Expression BuildConstant(Expression expression, bool isFromConstant)
		{
			// try to assign expression as constant expression
			ConstantExpression result = expression as ConstantExpression;
			// if result is not null (passed expression is not constant type)
			if (!isFromConstant)
			{
				result = Expression.Constant(Evaluate(expression));
			}
			// if constant is datetime, then convert to new datetime (because we don't support parsing of datetime
			if (result.Type == typeof(DateTime))
			{
				DateTime value = (DateTime)result.Value;
				ConstructorInfo constructor = typeof(DateTime).GetConstructor(new Type[] { typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int) });
				return Expression.New(constructor, Expression.Constant(value.Year), Expression.Constant(value.Month), Expression.Constant(value.Day),
					Expression.Constant(value.Hour), Expression.Constant(value.Minute), Expression.Constant(value.Second), Expression.Constant(value.Millisecond));
			}
			
			// add enum replace pattern
			if (result.Type.IsEnum)
			{
				string replacePattern = string.Format("([^\\.]|^)({0})([^\\.\\(\\w]|$)", result.ToString());
				if (!_SubstituteRegex.ContainsKey(replacePattern))
				{
					_SubstituteRegex.Add(
						replacePattern,
						string.Format("$1{0}.{1}.{2}$3",
							result.Type.Namespace,
							result.Type.Name,
							result.ToString()));
				}
			}

			return result;
		}

		/// <summary>
		/// Determines whether [is simplify type] [the specified expression].
		/// </summary>
		/// <param name="expression">The expression.</param>
		/// <returns>
		/// 	<c>true</c> if [is simplify type] [the specified expression]; otherwise, <c>false</c>.
		/// </returns>
		private bool IsSimplifyType(Expression expression)
		{
			// if expression is null or if expression is lambda, then we don't need to simplify
			if ((expression == null) 
                || (expression.Type.IsSubclassOf(typeof(LambdaExpression))))
			{
				return false;
			}
            Type testType = expression.Type;
            if (expression is MethodCallExpression)
            {
                testType = (expression as MethodCallExpression).Method.ReflectedType;
            }
			// simplife type means: not an anonymous type, not in namespace or assemblies pattern
            return IsAnonymousType(testType)
                || !(_NamespacePatterns.Contains(testType.Namespace)
                    || _Assemblies.Contains(testType.Module.Name)
                    || _Types.Contains(testType));
		}

		/// <summary>
		/// Determines whether [is anonymous type] [the specified type].
		/// </summary>
		/// <param name="type">The type.</param>
		/// <returns>
		/// 	<c>true</c> if [is anonymous type] [the specified type]; otherwise, <c>false</c>.
		/// </returns>
		private bool IsAnonymousType(Type type)
		{
			#warning // TODO check if type namse starts with <> only way how to check if type is anonymous???
			return type.Name.StartsWith("<>");
		}
	}
}
